package com.neural.infrastructure;

import java.util.LinkedList;
import java.util.List;

import com.neural.activation.Activation;
import com.neural.descriptor.NeuronDescriptor;
import com.neural.randomizer.Randomizer;

public class Neuron {

	private double value;
	private Double bias;
	private List<Connection> preConnections;
	private List<Connection> postConnections;

	public Neuron() {
		this.value = 0.0;
		this.preConnections = new LinkedList<Connection>();
		this.postConnections = new LinkedList<Connection>();
	}
	
	public Neuron(NeuronDescriptor neuronDescriptor, Layer previousLayer, Randomizer randomizer) {
		this();
		
		this.bias = (neuronDescriptor.getBias() != null ? neuronDescriptor.getBias() : randomizer.generateDouble());
		if(previousLayer != null) {
			this.createConnections(neuronDescriptor, previousLayer, randomizer);			
		}
	}
	
	private void createConnections(NeuronDescriptor neuronDescriptor, Layer previousLayer, Randomizer randomizer) {
		for(int i = 0; i < previousLayer.getNeurons().size(); i++) {
			Neuron preNeuron = previousLayer.getNeurons().get(i);
			Double weight = neuronDescriptor.getWeights().get(i);
			Connection connection = new Connection(preNeuron, (weight != null) ? weight : randomizer.generateDouble());
			preNeuron.addPostConnection(connection);
			this.addPreConnection(connection);
		}
	}

	public Double getBias() {
		return bias;
	}

	public void setValue(double value) {
		this.value = value;
	}

	public double getValue() {
		return value;
	}

	public void addPreConnection(Connection connection) {
		preConnections.add(connection);
	}

	public List<Connection> getPreConnections() {
		return preConnections;
	}

	public void addPostConnection(Connection connection) {
		postConnections.add(connection);
	}

	public void compute(Activation activation) {

		if (preConnections.size() > 0) {

			double newValue = bias;
			for (Connection connection : preConnections) {
				newValue += (connection.getWeight() * connection.getPreNeuron().value);
			}
			value = activation.getValueFor(newValue);
		}
	}

	public double optimalizeWeigths(double learningSpeed, double expected) {

		double error = expected - this.value;
		this.bias += (learningSpeed * error);

		for (Connection connection : preConnections) {
			connection.computeWeigth(learningSpeed, error);
		}
		return error;
	}
}
